/*
 * Copyright 2010
 * Jairou Software Organization
 * All Rights Reserved.
 * 
 * This software is the proprietary 
 * information of Roger Ramia 
 * 
 * and the Jairou Software Organization 
 * Use is subject to license terms.
 * 
 * For licensing information please visit
 * http://www.jairou.org/license
 * 
 * @author Roger Ramia
 */
package org.jairou.core;

import java.util.HashMap;
import java.util.Map;

/**
 * Common Exception handling utilities.
 * Provides the ability to access a common 'errors'
 * object within the provided context enabling the sharing
 * errors across various method invocations
 *
 * @author Roger Ramia
 */
public class Exceptions {

	// provides constants for setting errors/messages
	// within a given context
	public static final String ERRORS_KEY = "errors";
	public static final String MESSAGE_KEY = "message";
	
	/**
	 * Obtains the root exception message by repeatedly
	 * calling getCause on the supplied Throwable until
	 * it gets to the bottom of the StackTrace.  Once the
	 * root cause exception is identified, the getMessage()
	 * String is returned.  If no message is available at the
	 * root, it simply returns the Class name of the root exception.
	 * <br/>
	 * Additionally if the message originated in Javascript the 
	 * prefix 'sun.org.mozilla.javascript.internal.JavaScriptException:' is
	 * parsed out, resulting in the original message specified within the script.
	 * 
	 * @param Throwable the top level exception
	 * @return String - the message of the root cause exception
	 */
	public static String rootMessage(Throwable t) {
		if ("true".equalsIgnoreCase(XML.get("debug"))) t.printStackTrace();
		String errMsg = null;
		Throwable cause = t;
		while (cause != null) {
			errMsg = cause.getMessage();
			if (Strings.nullOrEmpty(errMsg)) errMsg = cause.getClass().getName();
			cause = cause.getCause();			
		}
		if (!Strings.nullOrEmpty(errMsg)){
			if (errMsg.indexOf("sun.org.mozilla.javascript.internal.JavaScriptException:") != -1) {
				errMsg = errMsg.substring(56);				
				if (errMsg.indexOf("(") != -1) errMsg = errMsg.substring(0, errMsg.indexOf("("));
				errMsg = errMsg.trim();
			}
		}
		return errMsg;
	}	
	
	/**
	 * Returns the 'errors' property of the given context
	 * if no errors property is found null is returned;
	 * 
	 * @param Object typically a Map with context parameters
	 * 				 but can also be a bean with a getErrors method
	 * 				 or a web scope object (request, session or application)
	 * 				 with an "errors" attribute
	 * @return Object typically a Map with error messages and Field level erros
	 * 		   can also be an Errors bean
	 */
	public static Object getErrors(Object ctx) {
		return Objects.get(ERRORS_KEY, ctx);
	}
	
	
	/**
	 * Checks to see if specified context has any pending Errors.
	 * i.e. if the getErrors does not return null for the given context
	 * 
	 * @param Object typically a Map with context parameters
	 * 				 but can also be a bean with a getErrors method
	 * 				 or a web scope object (request, session or application)
	 * 				 with an "errors" attribute

	 * @return boolean if the current context has an errors property
	 * 				   that is not null
	 */
	public static boolean hasErrors(Object ctx) {
		return (getErrors(ctx) != null);
	}
	
	
	/**
	 * Adds an error message to the current context's 'errors' property.
	 * If the current context does not contain any errors a new Errors Map 
	 * is created to hold errors.  Error messages are stored by the specified 
	 * key and placed into the context under the property name 'errors' a 
	 * subsequent call to getErrors() will return the Errors Map.
	 * 
	 * @param key - Property name for the current Error Message
	 * 				For field validation this is typically the failed field name
	 * @param message - Current error message usually the root message 
	 * 					of the cause exception
	 * @param ctx - Current context object which contains the 'errors' property
	 */
	public static void addError(String key, String message, Object ctx) {
		Map errors = (Map) getErrors(ctx);
		if (errors == null) errors = new HashMap();
		if (errors.containsKey(key)) {
			String delimiter = XML.get("exception-delimiter");
			if (Strings.nullOrEmpty(delimiter)) delimiter = " ";
			message+=delimiter+(String) errors.get(key);
		}
		errors.put(key, message);
		Objects.set(ERRORS_KEY, errors, ctx);
	}
	
	
	/**
	 * Overload addError method which adds an error message to the 
	 * current context's 'errors' property. If the current context does not contain
	 * any errors a new Errors Map is created to hold errors.  Error messages are 
	 * stored by a specified key and placed into a Map (or bean) context under 
	 * the property name 'errors' a  subsequent call to <code>getErrors</code> 
	 * will return the Errors object.
	 * 
	 * @param t Top level Throwable object.  Uses the root message
	 * 			  and a key of 'message' as to add the error to the 
	 * 			  current 'errors' property on the supplied context
	 * @param ctx Current context object which contains the 'errors' property
	 */
	public static void addError(Throwable t, Object ctx) {		
		addError(MESSAGE_KEY, Exceptions.rootMessage(t), ctx);
	}
}